from contextlib import asynccontextmanager
import hashlib
import logging
from urllib.parse import urljoin

import aiohttp

from webutils.compat import complex_json
from webutils.cache import region
from webutils.exceptions import (InvalidResource, FailedToAccessResource,
                                 ResourceKeyError)

from .base import ResourceManager

logger = logging.getLogger('server.manager.http')


class HTTPAPIManager(ResourceManager):
    base_url = None
    BASE_LOGGER = logger

    async def _do_request(self,
                          method,
                          path,
                          return_json=True,
                          use_cache=True,
                          **kwargs):
        """
        :Return: data, resp
        """
        method = method.lower()
        url = urljoin(self.base_url, path)
        base_msg = f'[method: {method}.upper()] [url: {url}]'

        cache_key = self._get_cache_key(method, url, **kwargs)
        if use_cache:
            cached_value = region.get(cache_key)
            if cached_value:
                self.logger.debug('%s hit cache', base_msg)
                if return_json:
                    return complex_json.loads(cached_value), None, base_msg
                return cached_value, None, base_msg
        session = self._get_http_session()
        try:
            resp = await session.request(method, url, **kwargs)
            resp_text = await resp.text()
            if use_cache:
                region.set(cache_key, resp_text)
            if return_json:
                return complex_json.loads(resp_text), resp, base_msg
            return resp_text, resp, base_msg
        except aiohttp.ClientError as exc:
            self.logger.error(
                '%s failed because %s',
                base_msg,
                exc,
                exc_info=True,
            )
            raise FailedToAccessResource(exc)
        except complex_json.JSONDecodeError as exc:
            self.logger.error(
                '%s failed to json loads %s',
                base_msg,
                exc,
                exc_info=True,
            )
            raise InvalidResource(exc)

    def _get_cache_key(self, method, url, **kwargs):
        parts = [method.lower(), url, complex_json.dumps(kwargs)]
        return hashlib.new('md5', '-'.join(parts).encode('utf8')).hexdigest()

    def _get_http_session(self):
        return self.service.connection.http_session

    @asynccontextmanager
    async def do_request(self, *args, **kwargs):
        resp_text, resp, summary = await self._do_request(*args, **kwargs)
        try:
            yield resp_text
        except KeyError as exc:
            self.logger.error(
                '%s failed to get key from response: %s. Exc: %s', summary,
                resp_text, exc)
            raise ResourceKeyError(f'failed to get key. Exc: {exc}')
